#include <iostream>
#include <XMLHelper.h>
#include <VML2SVG.h>
#include "DocumentTransform.h"
#include "UnzipDocx.h"
#include <stdio.h>
#include <vector>
#include <string>

#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
#endif


using namespace Utility;

//location used to create a temporary folder
//and extract the docx file
#if defined(_WIN32) || defined(WIN32)
	const char *tempDir = getenv("TMP");
#elif defined __linux__
	const char *tempDir = "/tmp";
#endif

//location of the operating system temp folder
static char tmpFolderPath[1024];
//name of the process created folder within tmpFolderPath
static char tmpFolderName[1024];


//--------------------------------------------------------------
//@Descr : Get the path where the XSLT is located
//@params: (none)	   
//@return: (void)
//--------------------------------------------------------------
#if defined(_WIN32) || defined(WIN32)
string GetXSLTPathWin(const char *envVariable, const char *pathPrefix, const char* pathSuffix, bool dirSearch)
{
	WIN32_FIND_DATA lpFindFileData;
	HANDLE hret = NULL;
	char szPathAppData[MAX_PATH];
	string prefixpath;
	string returnPath;
	string fullpath;	
	char *profilename = NULL;
	vector<char*> profiles;

	GetEnvironmentVariable(envVariable, szPathAppData, sizeof(szPathAppData));
	prefixpath.append(szPathAppData);
	prefixpath.append(pathPrefix);
	fullpath.append(prefixpath);
	fullpath.append("*");

	hret = FindFirstFileEx(fullpath.c_str(), FindExInfoStandard, &lpFindFileData, FindExSearchNameMatch , NULL, 0);
	
	if(hret != INVALID_HANDLE_VALUE)
	{
		if(strcmp(lpFindFileData.cFileName, ".") != 0)
		{
			profiles.push_back(lpFindFileData.cFileName);
		}
			
		while(FindNextFile(hret, &lpFindFileData))
		{
			if(strcmp(lpFindFileData.cFileName, "..") != 0)
			{
				//get all profile names
				profiles.push_back(lpFindFileData.cFileName);
			}
		}

		vector<char*>::iterator iter = profiles.begin();

		//Loop through profiles and find the plugin installation
		for(int i = 0; i < profiles.size(); i++)
		{
			profilename = *iter;			

			fullpath = strdup(prefixpath.c_str());
			if(dirSearch)
			{
				string s_pathprefix = pathPrefix;
				int count = (prefixpath.find_last_of(pathPrefix) +1) - s_pathprefix.length();
				fullpath = strdup(prefixpath.substr(0, count).c_str());
				fullpath.append("\\");
			}
			fullpath.append(profilename);
			fullpath.append(pathSuffix);

			hret = FindFirstFileEx(fullpath.c_str(), FindExInfoStandard, &lpFindFileData, FindExSearchNameMatch , NULL, 0);
			if(hret != INVALID_HANDLE_VALUE)
			{
				break;
			}
			fullpath.erase();
		}		
	}

	return fullpath;
}

void operaRegistryReader(const char *subkey, string &result)
{
	HKEY hk = 0;
    char buffer[255];
    DWORD dwType = 0;
    DWORD dwBufferSize = sizeof(buffer);

    if( RegOpenKey(HKEY_CURRENT_USER,subkey,&hk) == ERROR_SUCCESS)
    {
        dwType = REG_SZ;
        if( RegQueryValueEx(hk,"Plugin Path",0, &dwType, (BYTE*)buffer, &dwBufferSize) == ERROR_SUCCESS)
        {
            result.assign(buffer);
        }		
        RegCloseKey(hk);

    }	
}

void IERegistryReader(const char *subkey, string &result)
{
	HKEY hk = 0;
    char buffer[255];
    DWORD dwType = 0;
    DWORD dwBufferSize = sizeof(buffer);

    if( RegOpenKey(HKEY_LOCAL_MACHINE,subkey,&hk) == ERROR_SUCCESS)
    {
        dwType = REG_SZ;
        if( RegQueryValueEx(hk,"XSLTPath",0, &dwType, (BYTE*)buffer, &dwBufferSize) == ERROR_SUCCESS)
        {
            result.assign(buffer);
        }		
        RegCloseKey(hk);

    }	
}

#endif



//--------------------------------------------------------------
//@Descr : Constructor
//@params: (none)	   
//@return: (void)
//--------------------------------------------------------------
DocumentTransform::DocumentTransform()
{

	parserA = new XercesDOMParser();
	parserB = new XercesDOMParser();
	parserWithContext = new XercesDOMParser();	
	sourceFileName.clear();
	DOCParse = true;	
}


//--------------------------------------------------------------
//@Descr : destructor
//@params: (none)
//@return: (void)
//-------------------------------------------------------------
DocumentTransform::~DocumentTransform()
{
	delete parserA;
	delete parserB;
	delete parserWithContext;
}


//--------------------------------------------------------------
//@Descr : Creates a temp folder and stores the address of the 
//         temp folder in the path parameter
//@params: return parameter
//@return: (void)
//-------------------------------------------------------------

int DocumentTransform::createTempFolder(char *path)
{
	FileSystemUtil utilObj;
	//current milliseconds is used to create a unique temp folder
	unsigned long currentMillis = XMLPlatformUtils::getCurrentMillis();	
	sprintf(tmpFolderName, "DocX%u", currentMillis);

#if defined(_WIN32) || defined(WIN32)		
	sprintf(tmpFolderPath, "%s\\%s", tempDir, tmpFolderName);
#elif defined __linux__
	sprintf(tmpFolderPath, "%s/%s", tempDir, tmpFolderName);
#endif

	string strPath;
	strPath.assign(tmpFolderPath);
	utilObj.create_directory(strPath);
	strcpy(path, tmpFolderPath);
	return 1;
}

int DocumentTransform::removeTempFolder()
{
	FileSystemUtil utilObj;
	string strPath;
	strPath.assign(tmpFolderPath);
	utilObj.delete_directory(strPath);
	
	return 1;
}


//--------------------------------------------------------------
//@Descr : This method builds a DOM tree accessing all the parts
//         in /word folder (extracted from docx) in temp folder
//@params: (none)
//@return: 1 -> success
//        -1 -> failure 
//-------------------------------------------------------------
int DocumentTransform::parseWithContext()
{
	char contextPath[1024];
	bool documentParsed = false;
	sprintf(contextPath, "%s/[Content_Types].xml", tmpFolderPath);
	try
	{
		parserWithContext->parse(contextPath);
	}
	catch (const OutOfMemoryException&)
	{
		fprintf(stderr, "OutOfMemoryException.\n");        
		return -1;
	}
	catch (...)
	{
		fprintf(stderr, "parsing file failed at line %i\n", __LINE__);
		return -1;
	}
	xercesc::DOMDocument *doc = parserWithContext->getDocument();

	if (doc == NULL)
	{ return -1;}

	XMLCh *tagName = XMLString::transcode("Override");
	DOMNodeList *nodeList = doc->getElementsByTagName(tagName);
	int listLength = nodeList->getLength();

	memset(contextPath, '\0', strlen(contextPath));
	//check for all elements named PartName
	for(int i=0; i < listLength; i++) {
		DOMNode *nd = nodeList->item(i);
		XMLCh *attName = XMLString::transcode("PartName");
		const XMLCh* fileName = ((DOMElement*) nd)->getAttribute(attName);
		int isInsideWord = XMLString::patternMatch(fileName, XMLString::transcode("/word/"));
		if (isInsideWord != -1)
		{
			char* path = XMLString::transcode(fileName);						
			sprintf(contextPath, "%s%s", tmpFolderPath, path);
			int isDocumentFile = XMLString::patternMatch(fileName, XMLString::transcode("/word/document.xml"));
			if (!documentParsed) 
			{				
				if (isDocumentFile == -1)
					continue;
				if(!parseDocuments(contextPath))
				{ return -1;}
				documentParsed = true;
				i = 0;
				continue;
			}
			else
			{
				if (isDocumentFile == -1)
				{
					if(!parseDocuments(contextPath))
					{ return -1;}
				}
			}
		}
	}
	return 1;
}


//--------------------------------------------------------------
//@Descr : This method parses an individual file specified by the
//         argument. This method is called from parseWithContext()
//@params: path -> the location of the xml file to be parsed
//@return: 1 -> success
//        -1 -> failure 
//-------------------------------------------------------------
int DocumentTransform::parseDocuments(char *path)
{
	if (this->DOCParse)
	{
		//parse the main document (word\document.xml)
		try
		{
			parserA->parse(path);	
		}
		catch (const OutOfMemoryException&)
		{
			fprintf(stderr, "OutOfMemoryException.\n");        
			return -1;
		}
		catch (...)
		{
			fprintf(stderr, "parsing file failed at line %i\n", __LINE__);
			return -1;
		}
		this->DOMTree = parserA->getDocument();
		this->DOCParse = false;
	}
	else
		//parse the remaining documents
	{
		parserB->resetDocumentPool();
		try
		{
			parserB->parse(path);	
		}
		catch (const OutOfMemoryException&)
		{
			fprintf(stderr, "OutOfMemoryException.\n");        
			return -1;
		}
		catch (...)
		{
			fprintf(stderr, "parsing file failed at line %i\n", __LINE__);
			return -1;
		}
		xercesc::DOMDocument *DOMTree1 = parserB->getDocument();

		DOMNode *newNode1 = DOMTree1->getFirstChild();			
		DOMNode *newNode = parserA->getDocument()->importNode(newNode1, true);
		this->DOMTree->getLastChild()->appendChild(newNode);		
	}
	return 1;
}

//--------------------------------------------------------------
//@Descr : Method used to process images and relationships it uses
//         XMLHelper utility methods
//@params: (none)
//@return: 1 -> success
//        -1 -> failure 
//-------------------------------------------------------------

int DocumentTransform::preProcessParsedFile()
{
	char relsPath[1024];
	sprintf(relsPath, "%s/word/_rels/document.xml.rels", tmpFolderPath);

	XercesDOMParser *parser = new XercesDOMParser();
	try
	{
		parser->parse(relsPath);
		XMLHelper *helper = XMLHelper::getInstance();

		xercesc::DOMDocument *doc = parser->getDocument();

		if (doc == NULL)
		{return -1;}

		map<const XMLCh*, const XMLCh*> imgmap, relmap;

		helper->ReadRelationship(parser, &relmap, helper->LinkRelationshipUri);
		helper->ReadRelationship(parser, &imgmap, helper->ImageRelationshipUri);
		if (sourceFileName.size() > 0)
		{
			helper->UpdateImageNames(imgmap, sourceFileName, tmpFolderPath);
		}

		vector<DOMElement*> *res;
		xercesc::DOMDocument *mainDoc = parserA->getDocument();
		DOMElement *root = mainDoc->getDocumentElement();

		if (browserType == OPERA ||
			browserType == FIREFOX)
		{
		convertVML2SVG(&imgmap);
		}
		helper->HandleCoverPage(mainDoc);
//		helper->HandleColumns(mainDoc);
		helper->HandleNumberedLists(mainDoc);
		helper->HandleImages(mainDoc, imgmap,relmap);
		helper->HandleThemeFonts(mainDoc);
		helper->HandleLinks(mainDoc, relmap);
		helper->HandleTabIndentation(mainDoc);		


		parser->reset();
		delete parser;
	}
	catch(...)
	{
		parser->reset();
		delete parser;
		return -1;
	}
	return 1;
}

//--------------------------------------------------------------
//@Descr : Responsible for VML to SVG conversion defined in
//         VML2SVG.H
//@params: (none)
//@return: 1 -> success
//        -1 -> failure 
//-------------------------------------------------------------

int DocumentTransform::convertVML2SVG(map<const XMLCh*, const XMLCh*> *imgmap)
{
	VML2SVG vmlHelper;
	xercesc::DOMDocument *DOM_VML2SVG = parserA->getDocument();
	try
	{
		if(DOM_VML2SVG != NULL)
			vmlHelper.ConvertVML2SVG(DOM_VML2SVG, imgmap);
		else
		{return -1;}
	}
	catch(...)
	{
		return -1;
	}
	return 1;
}

//--------------------------------------------------------------
//@Descr : This method writes the merged DOM into a XML file. 		   
//@params: (none)
//@return: 1 -> success
//        -1 -> failure 
//-------------------------------------------------------------
int DocumentTransform::mergePackageDocuments()
{	
	char xmlPath[1024];
	preProcessParsedFile();	
	sprintf(xmlPath, "%s/parsedFile.xml", tmpFolderPath);

	try
	{
		// get a serializer, an instance of DOMWriter
		XMLCh tempStr[100];
		XMLString::transcode("LS", tempStr, 99);
		DOMImplementation *impl          = DOMImplementationRegistry::getDOMImplementation(tempStr);
		DOMWriter         *theSerializer = ((DOMImplementationLS*)impl)->createDOMWriter();

		// set feature if the serializer supports the feature/mode
		if (theSerializer->canSetFeature(XMLUni::fgDOMWRTSplitCdataSections, true))
			theSerializer->setFeature(XMLUni::fgDOMWRTSplitCdataSections, true);

		if (theSerializer->canSetFeature(XMLUni::fgDOMWRTDiscardDefaultContent, true))
			theSerializer->setFeature(XMLUni::fgDOMWRTDiscardDefaultContent, true);

		if (theSerializer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, false))
			theSerializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, false);

		if (theSerializer->canSetFeature(XMLUni::fgDOMWRTBOM, false))
			theSerializer->setFeature(XMLUni::fgDOMWRTBOM, false);

		//
		// Plug in a format target to receive the resultant
		// XML stream from the serializer.
		//
		XMLFormatTarget *myFormTarget;

		myFormTarget = new LocalFileFormatTarget(xmlPath);		

		// get the DOM representation
		DOMNode *doc = parserA->getDocument();

		if (doc == NULL)
		{ return -1;}

		//
		// do the serialization through DOMWriter::writeNode();
		//
		theSerializer->writeNode(myFormTarget, *doc);

		delete theSerializer;
		delete myFormTarget;
	}
	catch (const OutOfMemoryException&)
	{
		XERCES_STD_QUALIFIER cerr << "OutOfMemoryException" << XERCES_STD_QUALIFIER endl;	
		return -1;
	}
	catch (...)
	{
		fprintf(stderr, "parsing file failed at line %i\n", __LINE__);		
		return -1;
	}
	return 1;
}

//--------------------------------------------------------------
//@Descr : Transforms the XML file with XSLT and generats HTML
//@params: (none)
//@return: 1 -> success
//        -1 -> failure 
//-------------------------------------------------------------
int DocumentTransform::applyXSLTransform()
{	
	char xmlFilePath[1024];	
	sprintf(xmlFilePath, "%s/parsedFile.xml", tmpFolderPath);
	string returnpath;
	char outputFilePath[1024];	
	XMLHelper *helper = XMLHelper::getInstance();

	int result = -1;
	XalanTransformer theXalanTransformer;

	XALAN_USING_XERCES(XMLPlatformUtils)

	XALAN_USING_XERCES(XMLUni)
	XALAN_USING_XALAN(XalanTransformer)

#if defined(_WIN32) || defined(WIN32)

	if (browserType == OPERA)
	{
		sprintf(outputFilePath, "%s/output.xhtml", tmpFolderPath);
		operaRegistryReader("Software\\Opera Software", returnpath);
		returnpath.append("\\DocX2Html.xslt");		
	}

	if (browserType == FIREFOX)
	{
		const char *prefix = "\\Mozilla\\Firefox\\Profiles\\"; 
		char *suffix = "\\extensions\\OpenXMLViewer@Codeplex.com\\plugins\\DocX2Html.xslt";
		returnpath = GetXSLTPathWin("APPDATA", prefix, suffix, false);
		if(returnpath.size() <= 0)
		{
			const char *prefix_pf = "\\Mozilla";
			char *suffix_pf = "\\plugins\\DocX2Html.xslt";				
			returnpath = GetXSLTPathWin("PROGRAMFILES", prefix_pf, suffix_pf, true);		
		}
		sprintf(outputFilePath, "%s/output.xhtml", tmpFolderPath);
	}

	if (browserType == IE)
	{
		sprintf(outputFilePath, "%s/output.html", tmpFolderPath);
		IERegistryReader("Software\\OpenXMLViewer", returnpath);
		returnpath.append("\\DocX2Html_IE.xslt");
	}
	const char *xsltPath = returnpath.c_str();

#elif defined __linux__
	    memset(outputFilePath, '\0', strlen(outputFilePath));
		sprintf(outputFilePath, "%s/output.xhtml", tmpFolderPath);
		if (browserType == FIREFOX)
		{
			returnpath.append("/usr/lib/mozilla/plugins/DocX2Html.xslt");
		}
		if (browserType == OPERA)
		{
			returnpath.append("/usr/lib/opera/plugins/DocX2Html.xslt");
		}


#endif
	try
	{
		theXalanTransformer.eOmitMETATagYes;		
		result = theXalanTransformer.transform(xmlFilePath, 
			returnpath.c_str(), 
			outputFilePath);

		if(result != 0)
		{
			cerr << "SimpleTransform Error: \n" << theXalanTransformer.getLastError()
				<< endl
				<< endl;
			return -1;
		}
	}
	catch(...)
	{			
		cerr << "XALAN Initialization failed!" << endl;
		return -1;
	}

	return 1;
}


//--------------------------------------------------------------
//@Descr : Transforms the XML file with XSLT and generats HTML
//		   (the method is overloaded for commandline interface)
//@params: (none)
//@return: 1 -> success
//        -1 -> failure 
//-------------------------------------------------------------
int DocumentTransform::applyXSLTransform(char *xslt_path)
{
	char xmlFilePath[1024];
	char outputFilePath[1024];	
	sprintf(xmlFilePath, "%s/parsedFile.xml", tmpFolderPath);
	if (browserType == OPERA ||
		browserType == FIREFOX)
	{
		sprintf(outputFilePath, "%s/output.xhtml", tmpFolderPath);
	}
	else
	{
		sprintf(outputFilePath, "%s/output.html", tmpFolderPath);
	}
	string returnpath;
	returnpath.assign(xslt_path);	

	int result = -1;
	XalanTransformer theXalanTransformer;

	XALAN_USING_XERCES(XMLPlatformUtils)

	XALAN_USING_XERCES(XMLUni)
	XALAN_USING_XALAN(XalanTransformer)

#if defined(_WIN32) || defined(WIN32)

	if (browserType == OPERA ||
		browserType == FIREFOX)
	{
		returnpath = returnpath + "/DocX2Html.xslt";		
	}

	if (browserType == IE)
	{
		returnpath = returnpath + "/DocX2Html_IE.xslt";
	}
#elif defined __linux__
	returnpath.append("/usr/local/lib/DocX2Html.xslt");	
#endif

	try
	{
		theXalanTransformer.eOmitMETATagYes;
		result = theXalanTransformer.transform(xmlFilePath, 
			returnpath.c_str(), 
			outputFilePath);

		if(result != 0)
		{
			cerr << "SimpleTransform Error: \n" << theXalanTransformer.getLastError()
				<< endl
				<< endl;
			return -1;
		}
	}
	catch(...)
	{			
		cerr << "XALAN Initialization failed!" << endl;
		return -1;
	}

	return 1;
}


//--------------------------------------------------------------
//@Descr : This is the main entry point to docx parse and transform
//         module
//@params: (none)
//@return: 1 -> success
//        -1 -> failure 
//-------------------------------------------------------------

int DocumentTransform::convertDocumentToHTML()
{
	char tempInputFilePath[1024];
	UnzipDocx* unzipped=NULL;
	bool fileExtracted = false;

	try
	{
		sprintf(tempInputFilePath, "%s/input.docx", tmpFolderPath);		

		unzipped = new UnzipDocx(tempInputFilePath, tmpFolderPath);
		if (unzipped != NULL)
		{
			fileExtracted = unzipped->extract_all_files();
		}
		else
		{
#if defined(_WIN32) || defined(WIN32)
			MessageBox(0, "Failed to open the file.", "Error", 0);
#elif defined __linux__
			fprintf(stderr, "Failed to open the file.\n");
#endif
			return -1;
		}

		if (!fileExtracted)
		{
#if defined(_WIN32) || defined(WIN32)
			MessageBox(0, "Invalid File!", "Error", 0);
#elif defined __linux__
			fprintf(stderr, "Failed to open the file.\n");
#endif
			
			return -1;
		}

		if ( parseWithContext() == -1 ||
			 mergePackageDocuments() == -1 ||
			 applyXSLTransform() == -1 )			 
		{
			delete unzipped;
			return -1;
		}
	}
	catch (...)
	{
		fprintf(stderr, "Failed to transfrom the document: %i\n", __LINE__);
		delete unzipped;		
		return -1;
	}

	delete unzipped;		
		
	return 1;
}

int DocumentTransform::convertDocumentToHTML(char *xslt_path)
{
	char tempInputFilePath[1024];
	UnzipDocx* unzipped=NULL;
	bool fileExtracted = false;

	try
	{		
		sprintf(tempInputFilePath, "%s/input.docx", tmpFolderPath);		
		unzipped = new UnzipDocx(tempInputFilePath, tmpFolderPath);
		if (unzipped != NULL)
		{
			fileExtracted = unzipped->extract_all_files();
		}
		else
		{
			cout << "\n\n Error: Failed to open the file. Please check the format.\n";
			return -1;
		}

		if (!fileExtracted)
		{
#if defined(_WIN32) || defined(WIN32)
			cout << "\n\n Error: Failed to open the file. Please check the format.\n";
#elif defined __linux__
			cout << "\n\n Error: Failed to open the file. Please check the format.\n";
#endif
			
			return -1;
		}

		if ( parseWithContext() == -1 ||
			 mergePackageDocuments() == -1 ||
			 applyXSLTransform(strdup(xslt_path)) == -1 )			 
		{
			delete unzipped;
			return -1;
		}
	}
	catch (...)
	{
		fprintf(stderr, "Failed to transfrom the document: %i\n", __LINE__);
		delete unzipped;		
		return -1;
	}

	delete unzipped;				
	return 1;
}